package com.osard.screen.service;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.Surface;
import android.view.SurfaceControl;

import androidx.core.app.NotificationCompat;


import com.osard.screen.R;
import com.osard.screen.event.ScreenRecordFailedEvent;
import com.osard.screen.event.ScreenRecordStartEvent;
import com.osard.screen.event.ScreenRecordStopEvent;
import com.osard.screen.event.ScreenRecordSuccessEvent;

import org.greenrobot.eventbus.EventBus;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Date;
import java.util.UUID;

public class ScreenRecordService extends Service {

    private final static String TAG = ScreenRecordService.class.getSimpleName();
    private final static String CHANNEL_ID = ScreenRecordService.class.getName();

    public final static String RESULT_CODE = "RESULT_CODE";
    public final static String RESULT_DATA = "RESULT_DATA";

    public final static String VIDEO_PATH = "VIDEO_PATH";
    public final static String VIDEO_NAME = "VIDEO_NAME";

    /**
     * 通知ID，ID固定为“int最大值 - 1111”，与其他通知ID不可重复
     */
    protected final static int NOTICE_ID = Integer.MAX_VALUE - 1111;

    private MediaProjectionManager mMediaProjectionManager;

    private int mResultCode;
    private Intent mResultData;

    /**
     * 录制屏幕宽度
     */
    protected int mWindowWidth;
    /**
     * 录制屏幕高度
     */
    protected int mWindowHeight;
    /**
     * 录制屏幕DPI
     */
    protected int mScreenDensity;

    private VirtualDisplay mVirtualDisplay;
    private MediaProjection mMediaProjection;

    // 录制开始时间
    private Date startDate;
    private String mVideoPath;
    private String mVideoName;
    private Surface surface;
    private MediaCodec mMediaCodec;
    private MediaMuxer mMuxer;
    private boolean mIsQuit = true;
    private MediaCodec.BufferInfo mBufferInfo;
    private boolean mMuxerStarted = false;
    private int mVideoTrackIndex = -1;

    private boolean failedDestroy = false;

    public ScreenRecordService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //获取录屏服务
        mMediaProjectionManager = (MediaProjectionManager)
                getSystemService(Context.MEDIA_PROJECTION_SERVICE);

        mBufferInfo = new MediaCodec.BufferInfo();
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("不支持绑定此服务");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (null == intent) {
            return super.onStartCommand(intent, flags, startId);
        }
        //参数
        Bundle bundle = intent.getExtras();
        if (null == bundle) {
            return super.onStartCommand(intent, flags, startId);
        }

        //读取存放视频路径
        mVideoPath = bundle.getString(VIDEO_PATH);
        //读取存放视频名称
        mVideoName = bundle.getString(VIDEO_NAME);

        startForeground(NOTICE_ID, getForegroundNotice());

        //获取dpi
        mScreenDensity = getResources().getConfiguration().densityDpi;

        DisplayMetrics metrics = getResources().getDisplayMetrics();
        mWindowWidth = metrics.widthPixels;
        mWindowHeight = metrics.heightPixels;

        mResultCode = intent.getIntExtra(RESULT_CODE, 0);
        mResultData = intent.getParcelableExtra(RESULT_DATA);

        recordStart();

        return START_REDELIVER_INTENT;
    }

    /**
     * 继承service后重写此Notification方法，自定义自己的前台通知栏
     * 结束录制调用 {@link Service#stopSelf()}即可结束，不支持暂停和继续
     */
    protected Notification getForegroundNotice() {
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        //解决安卓8不显示通知问题
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    getString(R.string.media_record_service_app_name),
                    NotificationManager.IMPORTANCE_LOW);
            channel.setShowBadge(false);
            //震动不可用
            channel.enableVibration(false);
            //设置没有声音
            channel.setSound(null, null);
            notificationManager.createNotificationChannel(channel);
        }

        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);

        //设置标题
        mBuilder
                //设置内容
                .setContentText(getString(R.string.sevice_foreground_notice_sub_title))
                //设置true表示是一个正在活动的后台任务，不会被清除掉，比如音乐播放器
                .setOngoing(true)
                //震动不可用
                .setVibrate(null)
                //设置没有声音
                .setSound(null)
                // 强制设置不要折叠，下边2个属性
                .setGroupSummary(false)
                .setGroup("group")

                //设置小图标
                .setSmallIcon(R.mipmap.ic_screen_record_notice_logo)
                //设置通知时间
                .setWhen(System.currentTimeMillis());

        return mBuilder.build();
    }

    @Override
    public void onDestroy() {
        if (!failedDestroy) {
            recordStop();
        }

        super.onDestroy();
    }

    private void setUpVirtualDisplay() {
        mVirtualDisplay = mMediaProjection.createVirtualDisplay("RECORD-SCREEN-" + UUID.randomUUID().toString(),
                mWindowWidth, mWindowHeight, mScreenDensity,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                surface, null, null);
    }

    private void setUpMediaProjection() {
        mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
    }

    /**
     * 获取录制MediaFormat
     */
    protected MediaFormat getMediaFormat() {
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWindowWidth, mWindowHeight);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 6000 * 1000);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

        return mediaFormat;
    }

    // record
    private void configureMedia() {
        MediaFormat mediaFormat = getMediaFormat();
        try {
            mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        surface = mMediaCodec.createInputSurface();
        mMediaCodec.start();
    }

    public void recordStart() {
        //开始录制并未结束时，不可以再次开始
        if (!mIsQuit) {
            return;
        }

        setUpMediaProjection();
        configureMedia();
        setUpVirtualDisplay();

        //开启线程
        new Thread(this::startRecord).start();
    }

    private void startRecord() {
        try {
            EventBus.getDefault().post(new ScreenRecordStartEvent());

            //记录开始时间
            startDate = new Date();

            //创建文件路径
            createVideoPath();
            //创建文件
            mVideoName = createVideoFile(mVideoName);

            mIsQuit = false;
            mMuxer = new MediaMuxer(mVideoPath + mVideoName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            recordVirtualDisplay();
        } catch (Exception e) {
            e.printStackTrace();
            File tempFile = new File(mVideoPath + mVideoName);
            if (tempFile.exists()) {
                //noinspection ResultOfMethodCallIgnored
                tempFile.delete();
            }
            EventBus.getDefault().post(new ScreenRecordFailedEvent(e.getMessage(), e));
            //录制停止
            EventBus.getDefault().post(new ScreenRecordStopEvent());

            //录制失败时，不调用stop录制，即不触发成录制功
            failedDestroy = true;
            stopSelf();
        } finally {
            release();
        }
    }

    private void createVideoPath() {
        if (TextUtils.isEmpty(mVideoPath)) {
            //默认的保存文件夹
            mVideoPath = getExternalCacheDir().getPath() + File.separator + Environment.DIRECTORY_MOVIES + File.separator;
        } else {
            //若结尾不含路径标记，则追加标记
            mVideoPath = mVideoPath.endsWith(File.separator) ? mVideoPath : (mVideoPath + File.separator);
        }
        //创建缓存文件夹
        File fileFolder = new File(mVideoPath);
        if (!fileFolder.exists()) {
            //noinspection ResultOfMethodCallIgnored
            fileFolder.mkdirs();
        }
    }

    private String createVideoFile(String mVideoName) throws IOException {
        if (TextUtils.isEmpty(mVideoName)) {
            return System.currentTimeMillis() + ".mp4";
        } else {
            File file = new File(mVideoPath, mVideoName);
            if (file.exists()) {
                return createVideoFile(mVideoName.replace(".mp4", "") + "_1.mp4");
            } else {
                file.createNewFile();
            }
            return mVideoName;
        }
    }

    private void recordStop() {
        //已经停止时直接结束
        if (mIsQuit) {
            return;
        }

        mIsQuit = true;
    }

    private void recordVirtualDisplay() {
        while (!mIsQuit) {
            int index = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10000);
            if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                //后续输出格式变化
                resetOutputFormat();
            } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
                //请求超时
                try {
                    //noinspection BusyWait
                    Thread.sleep(10);
                } catch (InterruptedException ignore) {
                }
            } else if (index >= 0) {
                //有效输出
                if (!mMuxerStarted) {
                    throw new IllegalStateException("MediaMuxer dose not call addTrack(format) ");
                }
                encodeToVideoTrack(index);
                mMediaCodec.releaseOutputBuffer(index, false);
            }
        }
    }

    private void resetOutputFormat() {
        if (mMuxerStarted) {
            throw new IllegalStateException("output format already changed!");
        }
        MediaFormat newFormat = mMediaCodec.getOutputFormat();

        mVideoTrackIndex = mMuxer.addTrack(newFormat);
        mMuxer.start();
        mMuxerStarted = true;
    }

    private void encodeToVideoTrack(int index) {
        ByteBuffer encodedData = mMediaCodec.getOutputBuffer(index);

        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
            //是编码需要的特定数据，不是媒体数据
            mBufferInfo.size = 0;
        }
        if (mBufferInfo.size == 0) {
            encodedData = null;
        }
        if (encodedData != null) {
            encodedData.position(mBufferInfo.offset);
            encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
            mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);
        }
    }

    private void release() {
        mMuxerStarted = false;

        mResultCode = 0;
        mResultData = null;
        mIsQuit = true;

        if (mMediaCodec != null) {
            try {
                mMediaCodec.stop();
                mMediaCodec.release();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } finally {
                mMediaCodec = null;
            }
        }
        if (mVirtualDisplay != null) {
            try {
                mVirtualDisplay.release();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } finally {
                mVirtualDisplay = null;
            }
        }
        if (mMuxer != null) {
            try {
                mMuxer.stop();
                mMuxer.release();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } finally {
                mMuxer = null;
            }
        }
        cleanup();

        //如果结束录制时不是因为录制异常，则触发录制成功事件
        if (!failedDestroy) {
            EventBus.getDefault().post(new ScreenRecordSuccessEvent(mVideoPath + mVideoName, startDate));
            EventBus.getDefault().post(new ScreenRecordStopEvent());
        }
    }

    private void cleanup() {
        if (mMediaProjection != null) {
            mMediaProjection.stop();
        }
    }

}